import java.util.ArrayList;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

class XOR {
	
	private NeuralNet substrate;
	private NeuralNet cppn;
	private static double[][][] data = {{{0,0},{0},{0}},{{0,1},{1},{0}},{{1,0},{1},{0}},{{1,1},{0},{0}}};
  private BufferedWriter w;
  private int count = 0;
  private double min = Double.POSITIVE_INFINITY;
	
	public XOR(String[][] layoutS, String[][] layoutC) {
		substrate = new NeuralNet(multilayerSubstrate(layoutS), 1);
		cppn = new NeuralNet(NeuralNet.quickCPPN(layoutC), 1);
    try {
      w = new BufferedWriter(new FileWriter("RX/xx.csv"));
    } catch (IOException e) {
      System.err.println(e.getMessage());
    }
	}

	public static void main(String[] args) {
		String[][] layoutS = {{"B", "L", "L"},{"S"},{"S"}};
		String[][] layoutC = {{"B", "L", "L", "L", "L", "L", "L"}, {"S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"}, {"SS"}};
		XOR net = new XOR(layoutS, layoutC);
    
    /*double e;
    double[][][] x = net.data;
		do {
			e = net.substrate.trainNetwork(x, 1.0);
			System.out.println(e);
		} while (e > .01);
		for (int j = 0; j < x.length; j++) { 
			net.substrate.processInput(x[j][0]);
			double[] y = net.substrate.getOutputs();
			System.out.println(x[j][0][0]+" , "+x[j][0][1]+" : "+y[0]);
		}*/

    //net.writeCPPN();
    net.readCPPN();

    setSubstrateWeights(net.cppn, net.substrate);
    net.interleaved(10);

    try {
      net.w.close();
    } catch (IOException e) {
      System.err.println(e.getMessage());
    }
		for (int j = 0; j < data.length; j++) { 
			net.substrate.processInput(data[j][0]);
			double[] y = net.substrate.getOutputs();
			System.out.println(data[j][0][0]+" , "+data[j][0][1]+" : "+y[0]);
		}
    System.out.println("Train: "+net.substrate.evaluate(data) / data.length);
    System.out.println(net.count+ "\t"+ net.min);
	}
	
	public void interleaved(int thres) {
		int cppnFactor = 50;
		//setSubstrateWeights(cppn, substrate);
		double e = 0;
    double ex= substrate.evaluate(data) / data.length;
    double cppnFit = 0;
    //long time = System.currentTimeMillis();
		do {
      double subS = ex;
      double ep = Double.POSITIVE_INFINITY;
			for (int i = 0; i < thres; i++) {
				substrate.trainNetwork(data, 1.0, 0.7);//1.0
				e = substrate.evaluate(data) / data.length;
				System.out.println("Substrate: "+ e);
        if (e > ep) System.err.println("Substrate learn rate fail!");
        ep = e;
			}
      double subE = e;
			double[][][] cppnData = cppnData(substrate);
      ep = Double.POSITIVE_INFINITY;
			for (int i = 0; i < cppnFactor*thres; i++) {
				e = cppn.trainNetwork(cppnData, 0.01, 0.7/*Math.min(0.7, 1-cppnFit)*/) / cppnData.length;//0.01
        if (e > ep) System.err.println("CPPN learn rate fail!");
        ep = e;
				if (i == 0 || i == cppnFactor*thres-1) {
					System.out.println("CPPN: "+ e);			
        }
			}
			setSubstrateWeights(cppn, substrate);
      e = ex;
			ex = substrate.evaluate(data) / data.length;
      cppnFit = (subS - ex) / (subS - subE);
      try {
        //w.write(ex +", "+ (e-ex) +"\n");
        w.write(ex +", "+ cppnFit +"\n");
      } catch (IOException excp) {
        System.err.println(excp.getMessage());
      }
      System.out.println(ex +"\t"+(e-ex));
      
      count++;
      if (e-ex < min) min = e-ex;
		} while (ex > 0.09 || e-ex > Math.pow(10,-7));
	}
	
	//Note: No fix for bias nodes yet
	public double[][][] cppnData(NeuralNet net) {
		ArrayList<double[][]> d = new ArrayList<double[][]>();
		for (int i = 0; i < net.size(); i++) {
			for (int j = 0; j < net.size(i); j++) {
				Node n = net.getNode(i, j);
				for (int k = 0; k < n.size(); k++) {
					Node m = n.getConnection(k);
					double[][] e = {{n.getCoords()[0], n.getCoords()[1], m.getCoords()[0],
							m.getCoords()[1], n.getCoords()[0]-m.getCoords()[0],
							n.getCoords()[1]-m.getCoords()[1]},{n.getWeight(k)},{0}};
					//double[][] e = {{Math.sqrt(Math.pow(n.getCoords()[0]-m.getCoords()[0],2)+
					//		Math.pow(n.getCoords()[1]-m.getCoords()[1],2))}, {n.getWeight(k)},{i}};
					//double[][] e = {{n.getCoords()[0]-m.getCoords()[0],
					//    n.getCoords()[1]-m.getCoords()[1]/*, Math.floor((
					//		n.getCoords()[0]/10.0)*3), Math.floor((n.getCoords()[1]/10.0)*3)*/},
					//		{n.getWeight(k)},{i}};
					d.add(e);
				}
			}
		}
		double[][][] r = new double[d.size()][][];
		return d.toArray(r);
	}
	
	//Use CPPN c to set the weights of the substrate net
	public static void setSubstrateWeights(NeuralNet c, NeuralNet net){
		for (int i = 0; i < net.size(); i++) {
			for (int j = 0; j < net.size(i); j++) {
				Node n = net.getNode(i, j);
				for (int k = 0; k < n.size(); k++) {
					Node m = n.getConnection(k);
					double[] input = {n.getCoords()[0], n.getCoords()[1], 
							m.getCoords()[0], m.getCoords()[1], n.getCoords()[0]-m.getCoords()[0],
							n.getCoords()[1]-m.getCoords()[1]};
					//double[] input = {Math.sqrt(Math.pow(n.getCoords()[0]-m.getCoords()[0],2)+
					//		Math.pow(n.getCoords()[1]-m.getCoords()[1],2))};
					//double[] input = {n.getCoords()[0]-m.getCoords()[0],
					//		n.getCoords()[1]-m.getCoords()[1]/*, Math.floor((
					//		n.getCoords()[0]/10.0)*3), Math.floor((n.getCoords()[1]/10.0)*3)*/};
					
					c.processInput(input);
					n.setWeight(k, c.getOutputs()[0]);
				}
			}
		}
	}

  //Modded from quickCPPN
	public static String[][][] multilayerSubstrate(String[][] layers) {
		String[][][] x = new String[layers.length][][];
		for (int i = 0; i < layers.length; i++) {
			x[i] = new String[layers[i].length][3];
			for (int j = 0; j < layers[i].length; j++) {
				x[i][j][0] = ""+i;
				x[i][j][1] = ""+j;
				x[i][j][2] = layers[i][j];
			}
		}
		return x;
	}

  public void writeCPPN() {
    BufferedWriter cppnW;
    try {
      cppnW = new BufferedWriter(new FileWriter("RX/weights.csv"));
      for (int i = 0; i < cppn.size(); i++) {
        for (int j = 0; j < cppn.size(i); j++) {
          cppnW.write(i+","+j+":");
          for (int k = 0; k < cppn.getNode(i,j).size(); k++) {
            cppnW.write(k+","+cppn.getNode(i,j).getWeight(k)+"/");
          }
          cppnW.write("\n");
        }
      }
      cppnW.close();
    } catch (IOException e) {
      System.err.println(e.getMessage());
    }
  }

  public void readCPPN() {
    BufferedReader cppnR;
    try {
      cppnR = new BufferedReader(new FileReader("RX/weights.csv"));
      String l = cppnR.readLine();
      while (l != null){
        String[] s1 = l.split(":");
        if (s1.length > 1) {
          String[] s2 = s1[0].split(",");
          int i = Integer.parseInt(s2[0]);
          int j = Integer.parseInt(s2[1]);
          String[] s3 = s1[1].split("/");
          for (int k = 0; k < s3.length; k++) {
            String[] s4 = s3[k].split(",");
            int m = Integer.parseInt(s4[0]);
            double w = Double.parseDouble(s4[1]);
            cppn.getNode(i,j).setWeight(m,w);
          }
        }
        l = cppnR.readLine();
      }
    } catch (IOException e) {
      System.err.println(e.getMessage());
    }
  }
}
